package org.sean.framework.mybatis;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.sean.framework.code.StatusInfo;
import org.sean.framework.context.SpringApplicationContext;
import org.sean.framework.exception.StatusException;
import org.sean.framework.redis.RedisService;
import org.sean.framework.util.NumberUtil;
import org.sean.framework.util.StringUtil;

import java.lang.reflect.Field;
import java.security.SecureRandom;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.concurrent.TimeUnit;

/**
 * Integer Id 生成器
 *
 * @author sean
 */
@Slf4j
public class IntegerIdGenerator implements IdentifierGenerator {
    private static final String LATEST_ID = "latest_id";

    private RedisService redisService;
    private SqlSessionFactory factory;

    private SecureRandom random = new SecureRandom();

    public RedisService getRedisService() {
        if (redisService == null) {
            redisService = SpringApplicationContext.getBean(RedisService.class);
        }
        return redisService;
    }

    public SqlSessionFactory getSessionFactory() {
        if (factory == null) {
            factory = SpringApplicationContext.getBean(SqlSessionFactory.class);
        }
        return factory;
    }

    @Override
    public Integer nextId(Object entity) {
        Integer latestId = getLatestId(entity);
        if (latestId != null) {
            // 并发访问, 数据为提交时, id 使用
            return latestId + (int) (getRedisService().getOrderUniqueValue(LATEST_ID, 1, random.nextInt(20) + 1L, 30));
        }
        return null;
    }

    /**
     * 取最大ID
     */
    private Integer getLatestId(Object entity) {
        Integer id = getRedisService().getInt(LATEST_ID);
        if (NumberUtil.isInValidId(id)) {

            SqlSession session = null;
            Connection conn = null;
            PreparedStatement pst = null;
            ResultSet rs = null;
            try {
                session = getSessionFactory().openSession();
                conn = session.getConnection();
                pst = conn.prepareStatement(StringUtil.formatContent("select max({0}) from {1}", getIdName(entity), getTableName(entity)));
                pst.execute();
                rs = pst.getResultSet();
                if (rs.next()) {
                    id = rs.getInt(1);
                    getRedisService().putLong(LATEST_ID, id, 1, TimeUnit.MINUTES);
                    return id;
                } else {
                    return null;
                }
            } catch (Exception e) {
                getRedisService().delete(LATEST_ID);
                throw new StatusException(StatusInfo.databaseAccessFailed().setAnyMessage("生成自增ID失败"), e);
            } finally {
                try {
                    if (rs != null) {
                        rs.close();
                    }
                } catch (Exception e) {
                    // do nothing
                } finally {
                    try {
                        if (pst != null) {
                            pst.close();
                        }
                    } catch (Exception e) {
                        // do nothing
                    } finally {
                        try {
                            if (conn != null) {
                                conn.close();
                            }
                        } catch (Exception e) {
                            // do nothing
                        } finally {
                            if (session != null) {
                                session.close();
                            }
                        }
                    }
                }
            }
        } else {
            return id;
        }
    }

    /**
     * 取表名
     */
    private String getTableName(Object entity) {
        String tableName = null;
        TableName annotation = entity.getClass().getAnnotation(TableName.class);
        if (annotation != null) {
            tableName = annotation.value();
        } else {
            tableName = StringUtil.lowerFirst(entity.getClass().getSimpleName());
        }
        return tableName;
    }

    /**
     * 取主键名
     */
    private String getIdName(Object entity) {
        Class<?> clz = entity.getClass();
        TableId tableId;
        while (clz != null) {
            for (Field field : clz.getDeclaredFields()) {
                if ((tableId = field.getAnnotation(TableId.class)) != null) {
                    if (StringUtil.isNotEmpty(tableId.value())) {
                        return tableId.value();
                    } else {
                        return field.getName();
                    }
                }
            }
            clz = clz.getSuperclass();
        }

        return null;
    }

}
